# -*- coding: utf-8 -*-
"""
------------------------------------------------------------------------------
    File Name:  bs4_samp
    Author   :  wanwei1029
    Date     :  2018/5/7
    Desc     : 对于Tag对象，例如a,可能通过link["href"]来取对应属性的值。
------------------------------------------------------------------------------
"""
import bs4
import re
from bs4 import BeautifulSoup


def samp():
    """
    1：BeautifulSoup转换本地文件时，需要用open函数读取文件内容，此时如果UTF-8编码，要加在encoding参数，否则中文会乱码。
    2：BeautifulSoup将HTML文档转换成一个树结构，其中树的每个结点，都可以对应四个对象。Tag,NavigableString,BeautifulSoup,Comment
    Tag:就是html中的各种标记。soup.tagName，返回的就是第一个tag对象。tag对象的常用属性：
        contents，返回当前节点的直接子节点，返回结果是个list,结果中包括换行符，也是一个NavigableString对象。
        children，也是返回当前节点的直接子节点，不过返回的是一个list_iterator对象 。
        descendants，返回当前节点所有的子孙节点，返回的是一个generator对象，此时注意：<a href="www.baidu.com">百度</a>
                    a本身是一个对象Tag,百度是其子节点，也是一个对象：NavigableString。
        string,如果节点没有子节点，会返回节点对应的文字信息，有子节点返回就是None，注意空格回车也是子节点.
        strings属性，和descendants属性一样，也是一个generator，返回所有的文字信息，stripped_strings会去掉字符串的空格和空行。
                注意strings返回的对象是NavigableString,stripped_strings返回的对象是str.
        parent,父节点
        next_sibling 下一兄弟节点，previous_sibling上一兄弟节点，注意空行也算是一个节点，所以a的next_sibling为空行。
                next_siblings,previous_siblings可以迭代输出所有兄弟节点。
        next_element,previous_element,和上面唯一的区别是，不分层次，针对所有节点。同样也有next_elements和previous_elements
                既然是所有节点，对于链接a来说，里面的文字也是一个节点。
        name 返回tag的名子。
    """
    soup = BeautifulSoup(open("bs4_samp.html", encoding="utf-8"), "lxml", from_encoding="utf-8")
    print("==========contents============")
    print(type(soup.body.contents))
    for child in soup.body.contents:
        print(type(child))
        if type(child) == bs4.element.NavigableString:
            print(child.string == '\n')
        elif type(child) == bs4.element.Tag:
            print(child.string)
    print("===========children===========")
    print(type(soup.body.children))
    for child in soup.body.children:
        print(type(child))
        if type(child) == bs4.element.NavigableString:
            print(child.string == '\n')
        elif type(child) == bs4.element.Tag:
            print(child.string)
    print("===========descendants===========")
    print(type(soup.body.descendants))
    for child in soup.body.descendants:
        print(type(child))
        if type(child) == bs4.element.NavigableString:
            print(child.string)
        elif type(child) == bs4.element.Tag:
            print(child.string)
    print("===========string===========")
    print("a下面没有节点："+soup.a.string)
    print("div下面有子节点：返回None")
    print(soup.div.string)
    print("===========strings===========")
    for child in soup.body.strings:
        print(type(child))
        if type(child) == bs4.element.NavigableString:
            print(child.string)
        elif type(child) == bs4.element.Tag:
            print(child.string)
    print("===========stripped_strings===========")
    for child in soup.body.stripped_strings:
        print(type(child))
        print(child)
    print("===========parent===========")
    print(type(soup.div.parent))
    print(soup.div.parent)
    print("===========sibling===========")
    print(soup.a)
    # 下一个节点是空行，也就是\n.
    print(soup.a.next_sibling)
    print(soup.a.next_sibling.next_sibling)
    for child in soup.a.next_siblings:
        print(type(child))
        print(child)
    print("===========element===========")
    print(soup.a)
    # 下一个节点是a里面的元素，也是一个节点
    print(soup.a.next_element)
    # 再下一个才是空行。
    print(soup.a.next_element.next_element)
    for child in soup.a.next_elements:
        print(type(child))
        print(child)


def samp_find_all( test_id ):
    """
    find_all是bs最常用的方法，函数原型为：find_all(name, attrs, recursive, text, **kwargs),下面一一讲解:
    1:name 参数
        name参数可以查找所以名子为name的tag,字符串对象会被自动忽略，name参数可以是字符串，正则表达式，列表，True和方法。
    2:kwargs参数
        kwargs可以进行更细粒度的查找，比如查找id=xx的tag,或者链接包括特定字符的tag
        注意，如果要查找指定class的属性，由于class是python的关键字，所以要加下载线:calss_="default"。
        如果要过滤多个条件，可以同时使用多个属性，如find_all(class="default", re.compile("qq"))
    3:attrs参数
        有些tag的属性的搜索中不能使用，比如data-*,这时可以通过attrs参数来实现查找。
    4:text参数
        text参数用来查找字符串
    5:recursive参数
        find_all默认是查找所以子孙结点，如果只查找直接子节点，可以设置recursive=False
    :return:
    """
    soup = BeautifulSoup(open("bs4_samp.html", encoding="utf-8"), "lxml", from_encoding="utf-8")
    if test_id == 'name':
        print("===========name参数===========")
        print("返回所有的链接函数")
        print(soup.find_all("a"))
        print("返回所有以b开头的tag. 包含body,b")
        print(soup.find_all(re.compile("^b")))
        print("返回所有的title和div tag")
        print(soup.find_all(["title", "div"]))
        print("返回所有的tag")
        print(soup.find_all(True))
        print("返回有id属性的的tag")
        print(soup.find_all(hasId))
    elif test_id == 'kwargs':
        print("===========kwargs参数===========")
        print("返回id=firstDiv的tag")
        print(soup.find_all(id='firstDiv'))
        print("返回链接中包含qq的tag")
        print(soup.find_all(href=re.compile("qq")))
        print("返回所有包含id属性的tag，无论id值是多少")
        print(soup.find_all(id=True))
        print("返回class为default的类型为a的tag")
        print(soup.find_all('a', class_='default'))
    elif test_id == 'attrs':
        # 下面这样写在python中是非法的。
        # soup.find_all(data-foo="testData")
        print(soup.find_all(attrs={"data-foo": "testData"}))
    elif test_id == 'text':
        print("返回链接文字包含新字的tag")
        print(soup.find_all('a', text=re.compile("新")))


def hasId(tag):
    return tag.has_attr('id')


def get_chapters():
    base_url = "https://www.aikantxt.la"
    soup = BeautifulSoup(open("bs4_samp2.html", encoding="gbk"), "html.parser")
    # 取书名，作者，最后更新时间
    # book_name = ''
    # main_info = soup.find(id="maininfo").find(id="info")
    # for child in main_info.children:
    #     if type(child) == bs4.element.Tag:
    #         if child.name == "h1":
    #             book_name = child.string
    # author_node = main_info.find('p', text=re.compile("作    者"))
    # last_update_date_node = main_info.find('p', text=re.compile("最后更新"))
    # author = rem_prefix(author_node.string, '：')
    # last_update_date = rem_prefix(last_update_date_node.string, '：')
    # print(book_name)
    # print(author)
    # print(last_update_date)
    # 取分类
    head_node = soup.find("head")
    category = head_node.find(attrs={"property": "og:novel:category"})['content']
    book_name = head_node.find(attrs={"property": "og:novel:book_name"})['content']
    author = head_node.find(attrs={"property": "og:novel:author"})['content']
    update_time = head_node.find(attrs={"property": "og:novel:update_time"})['content']
    # 取所有章节
    # print(soup)
    chapter_list = soup.find_all("dd")
    print(len(soup.find_all("a")))
    print(len(chapter_list))
    processed_chapter_list = list()
    short_url_set = set()
    for chapter_item in chapter_list:
        # print(type(chapter_item))
        short_url = chapter_item.contents[0]['href']
        if short_url not in short_url_set:
            short_url_set.add(short_url)
            chapter_dict = dict()
            chapter_dict['url'] = base_url+short_url
            chapter_dict['title'] = chapter_item.contents[0].string
            chapter_dict['book_name'] = book_name
            chapter_dict['author'] = author
            chapter_dict['update_time'] = update_time
            chapter_dict['category'] = category
            processed_chapter_list.append(chapter_dict)
    print(len(processed_chapter_list))
    for chapter_item in processed_chapter_list:
        print(chapter_item)


def rem_prefix(msg, splitstr):
    if not (msg or splitstr):
        return
    return msg.split(splitstr)[1]

def other_samp():
    """
    除了find_all，还有find等很多方法，使用方式类似。
    另外，通过方法select,可以通过css语法选择元素。
    :return:
    """
    print("todo")


if __name__ == '__main__':
    test_method = "get_chapters"
    if test_method == "samp":
        samp()
    elif test_method == "samp_find_all":
        test_id = 'name'
        test_id = 'kwargs'
        test_id = 'attrs'
        test_id = 'text'
        samp_find_all(test_id)
    elif test_method == "get_chapters":
        get_chapters()
